package com.flyan.game.motafx.core.map;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.almasb.fxgl.dsl.FXGL;
import com.almasb.fxgl.entity.Entity;
import com.flyan.game.motafx.component.character.BraveComponent;
import com.flyan.game.motafx.config.GameConfig;
import com.flyan.game.motafx.core.enums.EntityClassifyEnum;
import com.flyan.game.motafx.core.enums.EntityTypeEnum;
import com.flyan.game.motafx.core.enums.ZLayerEnum;
import com.flyan.game.motafx.core.event.BaseEvent;
import com.flyan.game.motafx.core.event.enums.EventListenerTypeEnum;
import com.flyan.game.motafx.core.event.enums.EventTypeEnum;
import com.flyan.game.motafx.core.event.listener.MoveToTargetEventListener;
import com.flyan.game.motafx.core.function.MapTraverseConsumer;
import com.flyan.game.motafx.core.map.info.EntityProperty;
import com.flyan.game.motafx.core.map.info.FloorInfo;
import com.flyan.game.motafx.core.panel.GameInfoPanel;
import com.flyan.game.motafx.core.pojo.LifePanel;
import com.flyan.game.motafx.core.util.core.GameUtils;
import com.flyan.game.motafx.core.util.entity.EntityUtils;
import com.flyan.game.motafx.core.util.position.PositionUtils;
import com.flyan.game.motafx.entity.monster.MonsterEntityInfo;
import com.flyan.game.motafx.entity.monster.MonsterInfo;
import com.flyan.game.motafx.event.*;
import javafx.geometry.Point2D;
import javafx.util.Duration;
import lombok.Getter;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * 楼层地图
 *
 * @author flyan
 * @version 1.0
 * @email flyan_by_chenu@sina.com
 * @since 2025/1/31
 */
@Getter
public class FloorMap {

    /**
     * 楼层信息
     */
    private final FloorInfo floorInfo;

    /**
     * 地图信息，有多个图层信息
     */
    private final int[][] map = new int[ZLayerEnum.numberLayers()][GameConfig.MAP_GRID_COUNT * GameConfig.MAP_GRID_COUNT];

    /**
     * 实体地图
     */
    private final Entity[][] entityMap = new Entity[ZLayerEnum.numberLayers()][GameConfig.MAP_GRID_COUNT * GameConfig.MAP_GRID_COUNT];

    /**
     * 勇士所处位置
     */
    private MapPosition bravePosition;

    public FloorMap(String assetName) {
        InputStream inputStream;
        try {
            inputStream = FXGL.getAssetLoader().getURL("/assets/" + assetName).openStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String json = IoUtil.read(inputStream, StandardCharsets.UTF_8);
        JSONObject jsonObject = JSONUtil.parseObj(json);
        this.floorInfo = jsonObject.toBean(FloorInfo.class);
        initMap();
    }

    /**
     * 初始化地图
     */
    private void initMap() {
        /* 填充地图 */
        List<List<List<Integer>>> mapInfo = floorInfo.getMap();
        for (int z = 0; z < mapInfo.size(); z++) {
            var layer = mapInfo.get(z);
            for (int r = 0; r < layer.size(); r++) {
                var row = layer.get(r);
                for (int c = 0; c < row.size(); c++) {
                    int entityCode = row.get(c);
                    map[z][idx0(r, c)] = entityCode;
                }
            }
        }
        /* 初始化事件 */
        if (CollUtil.isNotEmpty(floorInfo.getEventListeners())) {
            for (int i = 0; i < floorInfo.getEventListeners().size(); i++) {
                JSONObject eventListenerJson = floorInfo.getEventListeners().getJSONObject(i);
                String type = eventListenerJson.getStr("type");
                EventListenerTypeEnum listenerType = EventListenerTypeEnum.valueOfCode(type);
                assert listenerType != null;
                switch (listenerType) {
                    case MOVE_TO_TARGET -> {
                        JSONObject targetJson = eventListenerJson.getJSONObject("target");
                        MapPosition target = new MapPosition(targetJson.getInt("r"), targetJson.getInt("c"));
                        MoveToTargetEventListener listener = new MoveToTargetEventListener(floorInfo.getNumber(), target);
                        JSONArray eventsJsonArray = eventListenerJson.getJSONArray("events");
                        for (int j = 0; j < eventsJsonArray.size(); j++) {
                            JSONObject eventJson = eventsJsonArray.getJSONObject(j);
                            EventTypeEnum eventType = EventTypeEnum.valueOfCode(eventJson.getStr("type"));
                            assert eventType != null;
                            BaseEvent event;
                            switch (eventType) {
                                case CUSTOM_EVENT -> event = new CustomEvent();
                                case GAME_SLEEP -> event = new GameSleepEvent();
                                case SET_PROPERTY -> event = new SetPropertyEvent();
                                case SPAWN_ENTITY -> event = new SpawnEntityEvent();
                                case REMOVE_ENTITY -> event = new RemoveEntityEvent();
                                case PLAY_SOUND -> event = new PlaySoundEvent();
                                case CUTSCENE -> event = new CutsceneEvent();
                                default -> throw new RuntimeException("!!!");
                            }
                            Integer time = eventJson.getInt("time");
                            if (time != null) {
                                event.setDuration(Duration.millis(time));
                            }
                            event.setProperties(eventJson.getJSONObject("properties"));
                            event.setScript(eventJson.getStr("script"));
                            listener.addEvent(event);
                        }
                        GameUtils.app().eventService.addListener(listener);
                    }
                    case INTERACT -> {  }
                }
            }
        }
    }

    /**
     * 通过行索引+列索引获取地图索引
     *
     * @param rowIdx 行索引
     * @param colIdx 列索引
     * @return 地图索引
     */
    private int idx0(int rowIdx, int colIdx) {
        return rowIdx * GameConfig.MAP_GRID_COUNT + colIdx;
    }

    /**
     * 通过行+列获取地图索引
     *
     * @param row 行
     * @param col 列
     * @return 地图索引
     */
    private int idx(int row, int col) {
        return idx0(row - 1, col - 1);
    }

    /**
     * 通过地图位置获取地图索引
     *
     * @param position 位置
     * @return 地图索引
     */
    private int idx(MapPosition position) {
        return idx(position.row(), position.col());
    }

    /**
     * 绘制地图
     */
    public void drawMap() {
        traverseMap((pos, entityType, entity) -> {
            if (entityType != null && entity == null) { // 地图存在实体类型且未生成实体时继续
                /* 实体信息 */
                var info = floorInfo.getEntityInfo(new EntityProperty.EntityPosition(
                        entityType.getZIndex().getIndex(), pos.row(), pos.col()));
                entity = EntityUtils.spawnToMap(entityType, pos, info);

                /* 若是勇士实体，记录位置并绑定楼层地图 */
                if (entityType == EntityTypeEnum.CHARACTER_BRAVE) {
                    bravePosition = pos;
                    entity.getComponent(BraveComponent.class).setFloor(this);
                }

                /* 记录实体地图 */
                entityMap[entityType.getZIndex().getIndex()][idx(pos)] = entity;
            }
            return true;
        });
        /* 重绘游戏信息面板 */
        GameInfoPanel.redraw();
    }

    /**
     * 生成实体到楼层
     *
     * @param entityProperty 实体属性
     */
    public void spawnEntity(EntityProperty entityProperty, boolean isActive) {
        EntityProperty.EntityPosition position = entityProperty.getPosition();
        /* 先检查地图此处是否已存在实体 */
        EntityTypeEnum entityType = EntityTypeEnum.valueOfCode(entityProperty.getEntityCode());
        if (entityType != null && entityType.getZIndex().getIndex() == position.getZ()) {
            var locateCode = map[entityType.getZIndex().getIndex()][idx(position.getR(), position.getC())];
            if (locateCode == 0) { // 没有实体，生成
                MapPosition mapPosition = new MapPosition(position.getR(), position.getC());
                /* 记录实体地图 */
                map[entityType.getZIndex().getIndex()][idx(mapPosition)] = entityType.getCode();
                /* 实体活跃时才需要生成 */
                if (isActive) {
                    /* 实体信息 */
                    Entity entity = EntityUtils.spawnToMap(entityType, mapPosition, isActive, entityProperty.getInfo());
                    /* 若是勇士实体，记录位置并绑定楼层地图 */
                    if (entityType == EntityTypeEnum.CHARACTER_BRAVE) {
                        bravePosition = mapPosition;
                        entity.getComponent(BraveComponent.class).setFloor(this);
                    }
                    /* 记录实体 */
                    entityMap[entityType.getZIndex().getIndex()][idx(mapPosition)] = entity;
                }
            }
        }
    }

    /**
     * 移除实体
     *
     * @param position 位置
     */
    public void removeEntity(EntityProperty.EntityPosition position) {
        /* 先检查地图此处是否存在实体 */
        var locateCode = map[position.getZ()][idx(position.getR(), position.getC())];
        if (locateCode > 0) {
            var entity = entityMap[position.getZ()][idx(position.getR(), position.getC())];
            if (entity != null) {
                entity.removeFromWorld();
            }
            entityMap[position.getZ()][idx(position.getR(), position.getC())] = null;
            map[position.getZ()][idx(position.getR(), position.getC())] = 0;
        }
    }

    /**
     * 清理实体地图
     */
    public void clearEntityMap() {
        traverseMap((pos, entityType, entity) -> {
            if (entity != null) {
                entity.removeFromWorld();
                entityMap[entityType.getZIndex().getIndex()][idx(pos)] = null;
            }
            return true;
        });
    }

    /**
     * @return 是否存在实体
     */
    public boolean hasEntity() {
        AtomicBoolean exist = new AtomicBoolean(false);
        traverseMap((position, entityType, entity) -> {
            if (entity != null) {
                exist.set(true);
                return false;
            }
            return true;
        });
        return exist.get();
    }

    /**
     * 地图遍历
     *
     * @param consumer 遍历消费函数
     */
    public void traverseMap(MapTraverseConsumer consumer) {
        for (int z = 0; z < ZLayerEnum.numberLayers(); z++) {
            for (int i = 0; i < GameConfig.MAP_GRID_COUNT; i++) {
                for (int j = 0; j < GameConfig.MAP_GRID_COUNT; j++) {
                    int entityCode = map[z][idx0(i, j)];
                    if (entityCode > 0) { // 0不指代任何实体
                        EntityTypeEnum entityType = EntityTypeEnum.valueOfCode(entityCode);
                        /* 实体代码正确且图层正确才需要消费 */
                        if (entityType != null && entityType.getZIndex().getIndex() == z) {
                            if (!consumer.accept(new MapPosition(i + 1, j + 1), entityType, entityMap[z][idx0(i, j)])) {
                                return;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取地图位置上的实体信息
     *
     * @param position 位置
     * @return 实体信息
     */
    public MapPositionEntityInfo getPositionEntityInfo(MapPosition position) {
        if (!position.isCorrect()) {
            return null;
        }

        Entity[] entities = new Entity[ZLayerEnum.numberLayers()];
        for (int z = 0; z < ZLayerEnum.numberLayers(); z++) {
            int entityCode = map[z][idx(position)];
            if (entityCode > 0) {
                Point2D point = PositionUtils.getMapPosition(position);
                assert point != null;
                List<Entity> entitiesAt = FXGL.getGameWorld().getEntitiesAt(point);
                for (Entity entity : entitiesAt) {
                    ZLayerEnum zIndex = ZLayerEnum.getByIndex(entity.getZIndex());
                    if (zIndex != null) {
                        entities[zIndex.getIndex()] = entity;
                    }
                }
            }
        }
        return new MapPositionEntityInfo(entities);
    }

    public void updateBravePosition(MapPosition position) {
        EntityTypeEnum braveEt = EntityTypeEnum.CHARACTER_BRAVE;
        /* 更新地图 */
        map[braveEt.getZIndex().getIndex()][idx(bravePosition)] = 0;
        map[braveEt.getZIndex().getIndex()][idx(position)] = braveEt.getCode();
        /* 更新实体地图 */
        var braveEntity = entityMap[braveEt.getZIndex().getIndex()][idx(bravePosition)];
        entityMap[braveEt.getZIndex().getIndex()][idx(bravePosition)] = null;
        entityMap[braveEt.getZIndex().getIndex()][idx(position)] = braveEntity;
        bravePosition = position;
    }

    /**
     * 勇士离开，更新地图
     */
    public void braveLeave() {
        EntityTypeEnum braveEt = EntityTypeEnum.CHARACTER_BRAVE;
        map[braveEt.getZIndex().getIndex()][idx(bravePosition)] = 0;
        entityMap[braveEt.getZIndex().getIndex()][idx(bravePosition)] = null;
    }

    /**
     * 勇士进入，更新地图
     */
    public void braveEnter(MapPosition position) {
        EntityTypeEnum braveEt = EntityTypeEnum.CHARACTER_BRAVE;
        Entity braveEntity = GameUtils.app().braveEntityLv.get();
        map[braveEt.getZIndex().getIndex()][idx(position)] = braveEt.getCode();
        entityMap[braveEt.getZIndex().getIndex()][idx(position)] = braveEntity;

        Point2D mapPoint = PositionUtils.getMapPosition(position);
        assert mapPoint != null;
        BraveComponent braveComponent = braveEntity.getComponent(BraveComponent.class);
        braveComponent.teleport(mapPoint);
        bravePosition = position;
        braveComponent.setFloor(this);
    }

    /**
     * @return 获取楼层所有怪物信息，去重并按编号排序
     */
    public List<MonsterInfo> getMonsterInfos() {
        List<Pair<EntityTypeEnum, MonsterInfo>> typeLifePanels = new ArrayList<>();
        Set<EntityTypeEnum> monsterTypeSet = new HashSet<>();
        traverseMap((position, entityType, entity) -> {
            if (entity != null && entityType.getClassify() == EntityClassifyEnum.MONSTER) {
                if (!monsterTypeSet.contains(entityType)) {
                    MonsterEntityInfo monsterEntityInfo = EntityUtils.getEntityInfo(entity, MonsterEntityInfo.class);
                    LifePanel lifePanel = monsterEntityInfo.getObject("lifePanel", LifePanel.class);
                    MonsterInfo info = new MonsterInfo();
                    info.setType(entityType);
                    info.setName(monsterEntityInfo.getName());
                    info.setLifePanel(lifePanel);
                    typeLifePanels.add(new Pair<>(entityType, info));
                }
                monsterTypeSet.add(entityType);
            }
            return true;
        });
        return typeLifePanels.stream()
                .sorted(Comparator.comparingInt(a -> a.getKey().ordinal()))
                .map(Pair::getValue)
                .collect(Collectors.toList());
    }

}
